package com.gitee.wsl.text.template

import com.gitee.wsl.func.BiFunction
import com.gitee.wsl.func.Function

/**
 * Allows rendering of templates
 *
 *
 *
 * In order to perform speedy template rendering, it is necessary
 * to get the template converted into this class.  The way to do
 * that is as follows.
 *
 *
 *
 * First, you will be started with some suitable template. The values
 * to be substituted are surrounded by double brackets.  Here's an example:
 *
 * <pre>
 * Hello, my name is {{name}}
</pre> *
 *
 *
 * Then, feed that string into [.buildProcessor], like
 * this:
 *
 * <pre>
 * `
 * `String input = "Hello, my name is {{name}}"
 * var helloProcessor = TemplateProcessor.buildProcessor(input);`
` *
</pre> *
 *
 *
 * Now that you have built a template processor, hold onto it! The generation of
 * the template processor is the costly activity.  After that, you can use it
 * efficiently, by feeding it a [Map] of key names to desired values. For
 * our example, maybe we want *name* to be replaced with *Susanne*.
 * In that case, we would do this:
 *
 * <pre>
 * `
 * `var myMap = Map.of("name", "Susanne");
 * String fullyRenderedString = helloProcessor.renderTemplate(myMap);`
` *
</pre> *
 *
 *
 * The result is: Hello, my name is Susanne
 *
 */
class TemplateProcessor
/**
 * Instantiate a new object with a list of [TemplateSection].
 */ private constructor(private val templateSections: List<TemplateSection>) {
    /**
     * Given a map of key names -> value, render a template.
     */
    fun renderTemplate(myMap: Map<String, String>): String {
        val sb = StringBuilder()
        for (templateSection in templateSections) {
            sb.append(templateSection.render(myMap))
        }
        return sb.toString()
    }

    companion object {
        /**
         * Builds a [TemplateProcessor] from a string
         * containing a proper template.  Templated values
         * are surrounded by double-curly-braces, i.e. {{foo}} or {{ foo }}
         */
        fun buildProcessor(template: String): TemplateProcessor {
            val tSections = ArrayList<TemplateSection>()
            var builder = StringBuilder()
            // this flag is to help us understand whether we are currently reading the
            // name of a template literal.
            // e.g. in the case of hello {{ name }}, "name" is the literal.
            var isInsideTemplateKeyLiteral = false
            var i = 0
            while (i < template.length) {
                val charAtCursor = template[i]
                if (charAtCursor == '{' && i + 1 < template.length  &&  template[i + 1] == '{') {
                    // we were outside, now we're inside.
                    isInsideTemplateKeyLiteral = true
                    i += 1
                    if (builder.isNotEmpty()) {
                        tSections.add(TemplateSection(null, builder.toString()))
                        builder = StringBuilder()
                    }
                    i++
                    continue
                }
                if (charAtCursor == '}'  &&  i + 1 < template.length && template[i + 1] == '}' && isInsideTemplateKeyLiteral) {
                    // we were inside, now we're outside.
                    isInsideTemplateKeyLiteral = false
                    i += 1
                    if (builder.isNotEmpty()) {
                        tSections.add(TemplateSection(builder.toString().trim { it <= ' ' }, null))
                        builder = StringBuilder()
                    }
                    i++
                    continue
                }
                builder.append(charAtCursor)

                /*
             if we're at the end of the template, it's our last chance to
             add a substring (we can't be adding to a key, since if we're
             at the end, and it's not a closing brace, it's a malformed
             template.
             */if (i == template.length - 1) {
                    if (isInsideTemplateKeyLiteral) {
                        // if we're exiting this string while inside a template literal, then
                        // we're reading a corrupted input, and we should make that clear
                        // to our caller.
                        val templateSample = if (template.length > 10) template.substring(0, 10) else template
                        throw TemplateParseException("parsing failed for string starting with $templateSample")
                    }
                    tSections.add(TemplateSection(null, builder.toString()))
                }
                i++
            }
            return TemplateProcessor(tSections)
        }

        fun renderTemplate(message: String, transformer: Function<String, Any?>): String? {
            return renderTemplate(message, "{", "}", transformer)
        }

        fun renderTemplateForScope(
            message: String, placeholderStart: String,
            placeHolderEnd: String, scope: Map<String, Any>,
        ): String? {
            return renderTemplate(message, placeholderStart, placeHolderEnd, Function<String, Any?> { o: Any -> scope[o] })
        }

        fun renderTemplate(
            message: String, placeholderStart: String,
            placeHolderEnd: String, transformer: Function<String, Any?>,
        ): String? {
            return renderTemplate2(message, placeholderStart, placeHolderEnd,
                BiFunction{ name, _ ->
                    transformer.apply(name)
                })
        }

        fun renderTemplate2(
            message: String,
            placeholderStart: String,
            placeholderEnd: String,
            transformer: BiFunction<String, StringBuilder, Any?>,
        ): String? {

            var pos = message.indexOf(placeholderStart)
            if (pos < 0) return message

            pos += placeholderStart.length
            var pos2 = message.indexOf(placeholderEnd, pos)
            if (pos2 < 0) return message

            val sb= StringBuilder(message.length + 32)
            sb.append(message, 0, pos - placeholderStart.length)
            do {
                var name = message.substring(pos, pos2).trim { it <= ' ' }
                // 增加非空判断，可用于调试诊断
                if (name.endsWith("!")) {
                    name = name.substring(0, name.length - 1)
                    val result = transformer.apply (name, sb)
                       /* ?: throw NopException(ERR_UTILS_TEMPLATE_VAR_NOT_ALLOW_NULL)
                            .param(ARG_MESSAGE, message)
                            .param(ARG_VAR_NAME, name)*/
                    sb.append(result)
                } else {
                    val result = transformer.apply(name, sb)
                    if (result != null) {
                        sb.append(result)
                    }
                }
                pos2 += placeholderEnd.length
                pos = message.indexOf(placeholderStart, pos2)
                if (pos < 0) {
                    sb.append(message.substring(pos2))
                    break
                }
                sb.append(message, pos2, pos)

                pos += placeholderStart.length
                pos2 = message.indexOf(placeholderEnd, pos)
                if (pos2 < 0) {
                    sb.append(message.substring(pos - placeholderStart.length))
                    break
                }
            } while (true)

            return sb.toString()
        }

    }
}

/**
 * Represents one item in the list that will eventually be cooked
 * up into a fully-rendered string.  This record is the magic
 * ingredient to an easy templating system. If it has a key,
 * then this object will be getting replaced during final string rendering.  If it has a substring,
 * then the substring gets concatenated unchanged when the final string
 * is rendered.
 */
data class TemplateSection(val key:String?, val subString:String?) {
    fun render(myMap: Map<String, String>): String {
        if (subString != null) {
            return subString
        }
        return  myMap[key] ?: throw TemplateRenderException("Missing a value for key {$key}")
    }
}

/**
 * This exception is thrown when we try to convert a string
 * template into a list of [TemplateSection] and
 * fail to parse something.
 */
class TemplateParseException(msg: String) : RuntimeException(msg) {}

/**
 * This exception is thrown when we try to render a string
 * template but fail to include a key for one of the key
 * values - that is, if the template is "hello {foo}", and
 * our map doesn't include a value for foo, this exception
 * will get thrown.
 */
class TemplateRenderException(msg: String) : RuntimeException(msg) {}